﻿Imports Framework

Friend Class Handler_AnalyzeAndFindChanges
    Private Shared mDaemon As System.Threading.Thread
    Private Shared mEventArgs As New Manager.AnalyzeAndFindChangesActivityEventArgs

    Public Shared Sub Start()
        mDaemon = New System.Threading.Thread(AddressOf BackupProcessingDaemon)
        mDaemon.IsBackground = True
        mDaemon.Name = "Backup Processing Daemon Thread"
        mDaemon.Priority = System.Threading.ThreadPriority.Lowest
        mDaemon.Start()
    End Sub
    Public Shared Sub [Stop]()
        If mDaemon IsNot Nothing Then
            mDaemon.Abort()
            mDaemon.Join(10000)
            mDaemon = Nothing
        End If
    End Sub

    Friend Shared Sub QueueJobIfNotPresentInQueue(ByVal job As Entities.Backup.Job)
        JobQueue.QueueJobIfNotPresentInQueue(job)
    End Sub

    Private Shared mCurrentJob As Entities.Backup.Job = Nothing
    Private Shared Sub BackupProcessingDaemon()
        Try
            Do
                mCurrentJob = Nothing
                Try
                    mCurrentJob = JobQueue.GetNextToProcess
                    If mCurrentJob Is Nothing Then
                        If mEventArgs.Job IsNot Nothing Then
                            mEventArgs.Job = Nothing
                            Manager.OnAnalyzeAndFindChangesActivity(mEventArgs)
                        End If
                        If Diagnostics.Debugger.IsAttached Then
                            System.Threading.Thread.Sleep(5 * 1000)
                        Else
                            System.Threading.Thread.Sleep(60 * 1000)
                        End If
                    Else
                        mEventArgs.FilesAnalyzed = 0
                        mEventArgs.Job = mCurrentJob
                        Manager.OnAnalyzeAndFindChangesActivity(mEventArgs)

                        Manager.OnJobStarting(mCurrentJob)
                        mCurrentJob.LastRunUtc = Now.ToUniversalTime
                        EntityHandlers.Backup.Jobs.Save(mCurrentJob)

                        Handler_AnalyzeAndFindChanges.Scan(mCurrentJob)
                        'ok, we finished without problems, so remove from queue
                        JobQueue.Dequeue(mCurrentJob)
                        'now, reschedule
                        mCurrentJob.NextRunUtc = EntityHandlers.Backup.Jobs.CalculateNextRunTime(mCurrentJob.Schedule)
                        EntityHandlers.Backup.Jobs.Save(mCurrentJob)
                    End If
                Catch ex As System.Threading.ThreadAbortException
                    If TypeOf ex.ExceptionState Is Entities.Backup.Job Then
                        System.Threading.Thread.ResetAbort()
                    Else
                        Exit Do
                    End If
                Catch ex As Exception
                    JobQueue.Dequeue(mCurrentJob)
                    If mCurrentJob.Actions IsNot Nothing AndAlso mCurrentJob.Actions.OnError IsNot Nothing Then
                        With mCurrentJob.Actions.OnError
                            If .RetryBeforeFailing Then
                                mCurrentJob.NextRunUtc = Now.ToUniversalTime.AddSeconds(.RetryDelayInSeconds)
                            Else
                                mCurrentJob.Enabled = False
                            End If
                        End With
                    Else
                        mCurrentJob.Enabled = False
                    End If
                    EntityHandlers.Backup.Jobs.Save(mCurrentJob)

                    Manager.OnJobException(mCurrentJob, ex)
                End Try
            Loop
        Catch ex As System.Threading.ThreadAbortException
        Catch ex As Exception
            Framework.Misc.HandleException(ex)
        End Try
    End Sub

    Private Shared Sub Scan(ByVal job As Entities.Backup.Job)
        EntityHandlers.Backup.StoredFiles.ResetTouched(job.Id)

        If job.Source IsNot Nothing AndAlso job.Source.Folders IsNot Nothing Then
            For Each clsFolder As Entities.Backup.SourceFolder In job.Source.Folders
                ScanFolder(job, clsFolder)
            Next
        End If
    End Sub

    Private Shared Sub ScanFolder(ByVal job As Entities.Backup.Job, ByVal folder As Entities.Backup.SourceFolder)
        Manager.OnJobInformation(job, String.Format("Scanning source folder ""{0}"" for changes...", folder.Path))

        Dim clsFilter As New Filter(folder.CustomFilter, job.Source.Filter, Settings.GlobalFileFilter)

        ScanFolderRecursive(job, folder.Path, clsFilter, folder.Recursive)
    End Sub

    Private Shared Sub ScanFolderRecursive(ByVal job As Entities.Backup.Job, ByVal currentPath As String, ByVal filter As Filter, ByVal recursive As Boolean)
        mEventArgs.CurrentDirectoryPath = currentPath
        Manager.OnAnalyzeAndFindChangesActivity(mEventArgs)

        For Each strFilePath As String In System.IO.Directory.GetFiles(currentPath)
            Dim clsFi As New System.IO.FileInfo(strFilePath)
            If filter.IsMatch(clsFi) Then
                'this file should be backed up... make sure it is...

                Dim aStoredFiles As Generic.List(Of Entities.Backup.StoredFile)
                aStoredFiles = EntityHandlers.Backup.StoredFiles.GetAllByJobIdAndFilePath(job.Id, clsFi.FullName)
                If Not IsUpToDate(aStoredFiles.FirstOrDefault, clsFi) Then
                    mEventArgs.ChangedFilesFound += 1
                    QueueUpdate(job, clsFi, aStoredFiles)
                End If
            End If
            mEventArgs.FilesAnalyzed += 1
            Manager.OnAnalyzeAndFindChangesActivity(mEventArgs)
        Next
        If recursive Then
            For Each strSubDirPath As String In System.IO.Directory.GetDirectories(currentPath)
                ScanFolderRecursive(job, strSubDirPath, filter, True)
            Next
        End If
    End Sub

    Private Shared Sub QueueUpdate(ByVal job As Entities.Backup.Job, ByVal fileInfo As System.IO.FileInfo, ByVal storedVersions As Generic.List(Of Entities.Backup.StoredFile))
        Handler_PackagingDaemon.QueueWorkItem(job, fileInfo, storedVersions)
    End Sub

    Private Shared Function IsUpToDate(ByVal storedFile As Entities.Backup.StoredFile, ByVal fileInfo As System.IO.FileInfo) As Boolean
        If storedFile Is Nothing Then Return False
        If storedFile.LastWriteTimeUtc <> fileInfo.LastAccessTimeUtc Then Return False
        If storedFile.FileSize <> fileInfo.Length Then Return False
        If storedFile.CreationTimeUtc <> fileInfo.CreationTimeUtc Then Return False
        If storedFile.Attributes <> fileInfo.Attributes Then Return False
        Return True
    End Function

    Private Class Filter
        Private mFolder As Entities.Backup.FileFilter
        Private mJob As Entities.Backup.FileFilter
        Private mGlobal As Entities.Backup.FileFilter
        Friend Sub New(ByVal folderFilter As Entities.Backup.FileFilter, ByVal jobFilter As Entities.Backup.FileFilter, ByVal globalFilter As Entities.Backup.FileFilter)
            mFolder = folderFilter
            mJob = jobFilter
            mGlobal = globalFilter
        End Sub

        Public Function IsMatch(ByVal fileInfo As System.IO.FileInfo) As Boolean
            Dim bGlobalVote As Nullable(Of Boolean) = CalculateVote(mGlobal, fileInfo)
            Dim bJobVote As Nullable(Of Boolean) = CalculateVote(mJob, fileInfo)
            Dim bFolderVote As Nullable(Of Boolean) = CalculateVote(mFolder, fileInfo)

            If bFolderVote.HasValue Then
                Return bFolderVote.Value
            ElseIf bJobVote.HasValue Then
                Return bJobVote.Value
            ElseIf bGlobalVote.HasValue Then
                Return bGlobalVote.Value
            Else
                Return False
            End If
        End Function

        Private Function CalculateVote(ByVal fileFilter As Entities.Backup.FileFilter, ByVal file As System.IO.FileInfo) As Nullable(Of Boolean)
            If fileFilter Is Nothing Then Return Nothing

            Dim aVotes As New Generic.List(Of Boolean)
            If fileFilter.IncludePatterns IsNot Nothing Then
                For Each clsPatt As Entities.MatchPattern In fileFilter.IncludePatterns
                    If clsPatt.IsMatch(file.Name) Then
                        aVotes.Add(True)
                        Exit For
                    End If
                Next
            End If
            If fileFilter.ExcludePatterns IsNot Nothing Then
                For Each clsPatt As Entities.MatchPattern In fileFilter.ExcludePatterns
                    If clsPatt.IsMatch(file.Name) Then
                        aVotes.Add(False)
                        Exit For
                    End If
                Next
            End If
            If (file.Attributes And System.IO.FileAttributes.System) = System.IO.FileAttributes.System Then
                If fileFilter.IncludeSystemFiles.HasValue Then
                    aVotes.Add(fileFilter.IncludeSystemFiles.Value)
                End If
            End If
            If (file.Attributes And System.IO.FileAttributes.Hidden) = System.IO.FileAttributes.Hidden Then
                If fileFilter.IncludeHiddenFiles.HasValue Then
                    aVotes.Add(fileFilter.IncludeHiddenFiles.Value)
                End If
            End If
            If aVotes.Count = 0 Then
                Return Nothing
            Else
                For Each bVote As Boolean In aVotes
                    If Not bVote Then Return False
                Next
                Return True
            End If

        End Function

        Public Function IncludeSystemFiles() As Boolean
            If mFolder IsNot Nothing AndAlso mFolder.IncludeSystemFiles.HasValue Then
                Return mFolder.IncludeSystemFiles.Value
            End If
            If mJob IsNot Nothing AndAlso mJob.IncludeSystemFiles.HasValue Then
                Return mJob.IncludeSystemFiles.Value
            End If
            If mGlobal.IncludeSystemFiles.HasValue Then
                Return mGlobal.IncludeSystemFiles.Value
            Else
                Return False
            End If
        End Function
        Public Function IncludeHiddenFiles() As Nullable(Of Boolean)
            If mFolder IsNot Nothing AndAlso mFolder.IncludeHiddenFiles.HasValue Then
                Return mFolder.IncludeHiddenFiles.Value
            End If
            If mJob IsNot Nothing AndAlso mJob.IncludeHiddenFiles.HasValue Then
                Return mJob.IncludeSystemFiles.Value
            End If
            If mGlobal.IncludeHiddenFiles.HasValue Then
                Return mGlobal.IncludeHiddenFiles.Value
            Else
                Return Nothing
            End If
        End Function

    End Class

    Public Shared Function IsJobActive(ByVal job As Entities.Backup.Job) As Boolean
        Return JobQueue.IsJobActive(job)
    End Function
    Public Shared Sub AbortJob(ByVal job As Entities.Backup.Job)
        JobQueue.AbortJob(job)
    End Sub
    Private Class JobQueue
        Private Shared mQueue As New Generic.List(Of Entities.Backup.Job)
        ''' <summary>
        ''' Adds the job to the end of the queue, unless the job is already in the queue
        ''' </summary>
        ''' <param name="job"></param>
        ''' <remarks></remarks>
        Friend Shared Sub QueueJobIfNotPresentInQueue(ByVal job As Entities.Backup.Job)
            SyncLock mQueue
                For Each clsJob As Entities.Backup.Job In mQueue
                    If clsJob.Id = job.Id Then Return
                Next
                mQueue.Add(job)
                mEventArgs.JobsInQueue = mQueue.Count
            End SyncLock
            Manager.OnAnalyzeAndFindChangesActivity(mEventArgs)
            Manager.UpdateJobStatus(job)
        End Sub
        ''' <summary>
        ''' Returns first in queue or nothing if queue is empty. Does NOT remove the item from the queue
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Friend Shared Function GetNextToProcess() As Entities.Backup.Job
            SyncLock mQueue
                If mQueue.Count > 0 Then
                    Dim clsRet As Entities.Backup.Job
                    clsRet = mQueue.Item(0)
                    'mQueue.RemoveAt(0)
                    Return clsRet
                Else
                    Return Nothing
                End If
            End SyncLock
        End Function
        ''' <summary>
        ''' Removes the job from the queue, regardless of the position...
        ''' </summary>
        ''' <param name="job"></param>
        ''' <remarks></remarks>
        Friend Shared Sub Dequeue(ByVal job As Entities.Backup.Job)
            SyncLock mQueue
                mQueue.Remove(job)
                mEventArgs.JobsInQueue = mQueue.Count
            End SyncLock
            Manager.OnAnalyzeAndFindChangesActivity(mEventArgs)
            Manager.UpdateJobStatus(job)
        End Sub

        Friend Shared Function IsJobActive(ByVal job As Entities.Backup.Job) As Boolean
            SyncLock mQueue
                Return mQueue.Contains(job)
            End SyncLock
        End Function
        Public Shared Sub AbortJob(ByVal job As Entities.Backup.Job)
            SyncLock mQueue
                If mQueue(0).Id = job.Id Then mDaemon.Abort(job)
                mQueue.Remove(job)
            End SyncLock
        End Sub

    End Class

End Class
